SERVEI D'EXCEPCIONS





Introducció

Propòsit

La gestió d'excepcions permet informar que s'ha produït un error al realitzar una petició. Aquest error podrà ser tractat adequadament i en cas necessari informar a l'usuari, llençar una traça, enviar un correu, etc.

La gestió correcta de les excepcions és molt important i crítica, però generalment la forma en la que es generen i gestionen les excepcions en les aplicacions és un dels aspectes més ignorats en el disseny de les mateixes. L'ús apropiat de les excepcions fa que els nostres aplicatius siguin més robusts, més fàcils de desenvolupar i mantenir, més lliures d'errors i més fàcils d'utilitzar. Per aquest motiu és important que donem el màxim de detall en les excepcions.

Per evitar l'ús innecessari de blocs 'try-catch' dins el codi dels nostres aplicatius Canigó proporciona un mecanisme d'intercepció, pel qual indicarem quines excepcions volem tractar i quins gestors les tractaran sense haver d'incorporar cap referència a cap classe de l'aplicació.

Context i Escenaris d'Ús

El Servei de Traces es troba dins dels serveis de Propòsit General de Canigó.

El seu ús és imprescindible en Canigó, ja que tots els serveis utilitzen la gestió d'excepcions definida per aquest servei.

Versions i Dependències

Les dependències descrites a la següent url són requerides per tal de compilar i fer funcionar el projecte:
Dependències Servei de d'Excepcions

A qui va dirigit

Aquest document va dirigit als següents perfils:

  1. Programador. Per conéixer l'ús del servei
  2. Arquitecte. Per conéixer quins són els components i la configuració del servei
  3. Administrador. Per conéixer com configurar el servei en cadascun dels entorns en cas de necessitat

Documents i Fonts de Referència

Glossari

AOP (Aspect Oriented Programming)

AOP permet definir funcionalitats comunes a diferents components d'un aplicatiu de forma única i amb un tractament general i localitzat, mitjançant la intercepció de la funcionalitat pròpia del mateix. Aquestes funcionalitats comunes es denominen aspectes.

Aspectwerkz

AspectWerkz és un framework AOP que ofereix gran simplicitat permetent la definició d'aspectes, advices i introduccions amb classes POJOs. A més permet que els aspectes puguin ser definits mitjançant anotacions Java 5, doclets per JDK 1.3 i 1.4 o fins i tot mitjançant un fitxer XML simple.

El codi d'intercepció s'afegeix de forma automàtica al bytecode de la classe original mitjançant una compilació adicional. Aquest 'bytecode' modificat canvia la forma de cridar els nostres mètodes sense canviar la funcionalitat, i afegir així els conceptes importants en la programació orientada a aspectes: joinpoint, advice, etc...

Checked Exception

Tipus d'excepció que hereta de java.lang.Exception. El llenguatge Java obliga a què aquestes excepcions hagin de ser capturades mitjançant un bloc 'try-catch' o bé declarar al mètode que es torna a llençar l'excepció (finalment algú l'haurà de capturar) mitjançant la clàusula 'throws'.

Unchecked Exception

A diferència de les checked exceptions, poden ser ignorades i per tant no és necessari capturar-les ni declarar-les, encara que com veurem posteriorment, és una bona pràctica declarar-les en la signatura del mètode.

Descripció Detallada

Arquitectura i Components

Canigó ofereix una arquitectura d'excepcions totalment deslligada de qualsevol implementació. Únicament es basa en les característiques de les excepcions del llenguatge Java.

Els components podem classificar-los en:

  1. Components per representar diferents tipus d'excepcions
  2. Components per donar detall de les excepcions
  3. Components per a la gestió de les excepcions
  4. Components d'integració AOP (interns arquitectura)

Es pot trobar tota la documentació JavaDoc i el codi font referent aquests components a les següents urls:

JavaDoc: http://canigo.ctti.gencat.net/confluence/canigodocs/site/canigo2_0/canigo-services-exceptions/apidocs/index.html
Codi Font:  http://canigo.ctti.gencat.net/confluence/canigodocs/site/canigo2_0/canigo-services-exceptions/xref/index.html

Instal.lació i Configuració

Instal.lació

La instal.lació del servei requereix de la utilització de la llibreria 'canigo-services-exceptions' i les dependències indicades a l'apartat 'Introducció-Versions i Dependències'.

Configuració

La configuració del Servei d'Excepcions implica 3 pasos:

  1. Definir els gestors d'excepcions
  2. Definir el mapeig entre tipus d'excepcions i els gestors que les tractaran
  3. Definir el mecanisme automàtic d'intercepció (per integrar les 2 definicions anteriors)

1. Definició dels gestors d'Excepcions

Path desenvolupament: main/resources/spring/canigo-services-exceptions.xml
Path deployment web: WEB-INF/classes/spring/canigo-services-exceptions.xml

Per cada gestor d'excepcions que volem crear (es pot crear un únic per una classe pare d'excepció, una per cada subtipus d'excepció, ..) crearem un bean de configuració amb la següent informació:

Atributs:

Atributs Requerit Descripció
id Identificador del gestor
class Classe gestora que implementa la interfície 'ExceptionHandler'

Podem afegir propietats a cadascun dels gestors (per exemple el seu Servei de Traces) dins la definició del gestor.

Exemple:

<bean id="systemExceptionHandler"  class="net.gencat.ctti.canigo.services.exceptions.handlers.SystemExceptionHandler">
   <property  name="loggingService">
      <ref local="loggingService"/>
   </property>
</bean>

2. Definició del mapeig de tipus d'excepcions i gestors

Path desenvolupament: main/resources/spring/canigo-services-exceptions.xml
Path deployment web: WEB-INF/classes/spring/canigo-services-exceptions.xml

Atributs:

Atributs Requerit Descripció
id Usar 'net.gencat.ctti.canigo.services.exceptions.aop.aspectwerkz.ExceptionHandlerAspect'
class Usar 'net.gencat.ctti.canigo.services.exceptions.aop.aspectwerkz.ExceptionHandlerAspect'
singleton Especificar 'true'. D'aquesta forma es crearà una instància per a cada petició

Propietats:

Propietat Requerit Descripció
exceptionHandlers Mapa de clau-valor on la clau ha de ser la classe excepció full qualified i el valor la referència al gestor.

Exemple:

<bean id="net.gencat.ctti.canigo.services.exceptions.aop.aspectwerkz.ExceptionHandlerAspect"
class="net.gencat.ctti.canigo.services.exceptions.aop.aspectwerkz.ExceptionHandlerAspect"
singleton="false">

   <property name="exceptionHandlers">
    <map>
     <entry>
      <key><value>net.gencat.ctti.canigo.services.exceptions.SystemException</value></key>
      <ref  bean="systemExceptionHandler"/>
      <key><value>net.gencat.ctti.canigo.services.mail.exception.MailServiceException</value></key>
      <ref  bean="unAltreExceptionHandler"/>
      ...
     <!-- tantes entrades com gestors -->
     </entry>
    </map>
   </property>
</bean>


A l'exemple es mostra com s'ha configurat el gestor 'systemExceptionHandler' per l'excepció de tipus 'net.gencat.ctti.canigo.services.exceptions.SystemException'.

2. Definició del mecanisme d'intercepció automàtica

Path desenvolupament: main/resources/aop.xml
Path deployment web: WEB-INF/classes/aop.xml

Aquest fitxer defineix: les característiques de l'AOP AspectWerkz per definir l'aspecte. Per a més informació es recomana consultar 'http://aspectwerkz.codehaus.org/'.

Definirem els següents elements:

  • Aspecte
  • Advice. En quins punts s'afegeix l'aspecte


  • Aspecte

Definir els següents atributs:

Atributs Requerit Descripció
class Classe que defineix l'aspecte. Utilitzar: 'ExceptionHandlerAspect'
container Contenidor d'integració

Usar 'net.gencat.ctti.canigo.services.exceptions.aop.aspectwerkz.container.SpringAspectContainer'

ExceptionHandlerAspect: classe que defineix la funcionalitat comuna implementada per aquest aspecte.

L'atribut container indica la classe que proporciona les instàncies del gestor

<aspect class="ExceptionHandlerAspect" 
 container="net.gencat.ctti.canigo.services.exceptions.aop.aspectwerkz.container.SpringAspectContainer">
</aspect>
  • Advice. Definició de la intercepció

Definir els següents atributs:

Atributs Requerit Descripció
type Usar 'after throwing(exception)' per indicar que volem realitzar la intercepció en qualsevol punt de l'aplicació en el que es llenci una excepció
bind-to Usar 'execution(* *.*(..))' per indicar que interceptarem el llençament d'excepcions que es faci des de qualsevol mètode.
En l'exemple, l'expressió regular indica que s'interceptaran les excepcions llançades des de qualsevol mètode de qualsevol classe de qualsevol paquet; però es podria indicar que s'interceptessin, per exemple, les excepcions llençades des de classes DAO
name Usar 'handleException(java.lang.Throwable exception)' per indicar que es cridi a aquest mètode de l'aspecte



A la següent figura es presenta un exemple d'aquest fitxer:

<aspectwerkz>
   <system id="ExceptionHandlingAspect">
      <package name="net.gencat.ctti.canigo.services.exceptions.aop.aspectwerkz">
         <aspect class="ExceptionHandlerAspect" 
           container="net.gencat.ctti.canigo.services.exceptions.aop.aspectwerkz.container.SpringAspectContainer">
            <advice type="after throwing(exception)" bind-to="execution(* *.*(..))"  
               name="handleException(java.lang.Throwable exception)"/>
	 </aspect>
      </package>
   </system>
</aspectwerkz>


L'expressió regular definida dins la propietat bind-to hauria de ser qualsevol expressió vàlida d'acord amb 'Join point selection pattern language', que es pot consultar a la següent url: http://aspectwerkz.codehaus.org/definition_issues.html; tanmateix, ens hem d'assegurar que el nom complet package+classe està inclòs en l'expressió regular. Si no es troba cap coincidència amb el patró donat, AspectWerkz no serà capaç d'identificar correctament el mètode. Per exemple, si es volguessin interceptar únicament els mètodes dins d'una classe DAO, el patró seria: execution(* DAO.(..))


Utilització del Servei

Creació d'Excepcions

La creació d'excepcions és un aspecte de disseny molt important. Quants tipus d'excepcions generar?

La resposta no és única i si bé existeixen diferents possibilitats la recomanació és:

  1. Definir una excepció base per les excepcions d'un subsistema en concret. Per exemple, podem definir un tipus d'excepció per un módul concret de l'aplicació
  2. Definir una excepció específica per cada classe de negoci. D'aquesta forma, podrem determinar de forma més senzilla des de quin punt s'ha llençat una excepció i realitzar tractaments diferenciats

A més, seguirem els següents patrons d'ús:

  1. Si l'excepció base o específica és de negoci, aquesta haurà d'heretar de 'BusinessException'
  2. Si l'excepció base o específica és d'aspectes de serveis o infrastructura (DAOs, serveis d'integració, etc.), aquesta haurà d'heretar de 'SystemException'

Declaració d'Excepcions llençades als mètodes

Segons si l'excepció és de subtipus 'BusinessException' o de subtipus 'SystemException' es seguiran diferents estratègies:

  • Excepció de subtipus 'BusinessException'

En cas de que el mètode llenci una excepció de subtipus 'BusinessException' declarar 'throws Exception' (excepció genèrica).

Com hem comentat, BusinessException és una excepció de tipus 'checked', el que implica que segons el mecanisme tradicional de Java qualsevol classe que rebi l'excepció hauria de control.lar-la mitjançant 'try-catch' o declarar-la de nou als seus mètodes (ja que si no es fa així es produeix un error de compilació).
En els mecanismes tradicionals de Java, capturar una excepció de tipus checked implica afegir un control poc útil d'encapsulació de l'excepció rebuda en una nova excepció. Aquest control és degut a la pràctica poc correcta d'haver de gestionar obligatòriament les excepcions rebudes.

Segons el comentat, evitem el tractament innecessari declarant l'excepció amb 'throws Exception'. Aquesta incorporació és necessària per a que el compilador no es queixi de que no s'ha declarat l'excepció.

Exemple:

public interface AccountBO {

public void save(Account account) throws  Exception;

public void saveOrUpdate(Account account) throws  Exception;

public void update(Account account) throws Exception;

public  void delete(Account account) throws Exception ;

public Account load(Account  account);

}



  • Excepció de subtipus 'SystemException'

Si el mètode llença una excepció de tipus pare 'SystemException' hem de mantenir l'excepció llançada, ja que aquestes excepcions, per ser de tipus 'unchecked' no requereixen del control al nivell superior.

public void send(String from, String subject, String aMessage, boolean isHtml, String to) throws MailServiceException;

Generació d'Excepcions

La generació d'excepcions es realitza mitjançant la clàusula 'throws' de Java.

En la generació de les excepcions afegirem el màxim de detall possible mitjançant l'ús de la classe 'ExceptionDetails' (consultar l'apartat 'Arquitectura i Components' per a més informació). Aquesta classe permet que es pugui informar entre d'altres del nivell (Level), de la capa (Layer) i del subsistema (Subsystem) en què s'ha produit l'excepció que llencen l'excepció. A més, es pot informar un codi d'error (que tindrà la seva descripció en un fitxer de propietats) i unes propietats adicionals que serveixen per depurar l'error.

El patró recomanat per llençar una excepció és el mostrat a continuació:

ExceptionDetails exDetails = new ExceptionDetails(...)

exDetails.setProperties(...);
...
throw new XXXException(exDetails);


Encapsulació d'Excepcions rebudes de terceres parts

Si volem integrar tercers components externs a Canigó és convenient que s'encapsulin les excepcions rebudes en el mecanisme d'excepcions de Canigó. El patró de tractament pot ser com el mostrat a continuació:

try {    ...
    cridaServeiExtern();
} catch(ServeiExternException  ex) {
    ExceptionDetails exDetails = new ExceptionDetails("codiExcepcio",
    ...,Layer.XXXX,Subsystem.XXXX);
    exDetails.setProperties(mailProperties);
    throw  new XXXCanigoException(ex,exDetails);}


Com veiem, capturem l'excepció del component de tercers i li afegim la informació necessària. Per últim llencem l'excepció (important rellançar-la) incorporant com a segon paràmetre els detalls que hem indicat i en el primer paràmetre l'excepció arrel del tercer component.
Exemple:
package net.gencat.ctti.canigo.services.mail.impl;
...
public  class SpringMailServiceImpl implements MailService {
...
public void send(String from, String subject,  String aMessage, boolean isHtml,
Map recipients, List attachments) throws  MailException {
...
    try {
        ...
        message = this.mailSender.createMimeMessage();
        ...
        helper.setFrom(from);
        helper.setSubject(subject);
        helper.setText(aMessage,isHtml);
        ...
        this.mailSender.send(message);
    }
    catch(MessagingException  ex){
        ExceptionDetails exDetails = new 
          ExceptionDetails("canigo.services.mail.error_preparing_addresses",
          null,Layer.SERVICES, Subsystem.MAIL_SERVICES);
          exDetails.setProperties(mailProperties);
      
       throw new MailServiceException(ex,exDetails);
    }
    catch(Exception  ex){
        ExceptionDetails exDetails = new  
          ExceptionDetails("canigo.services.mail.error_sending_mail",
          null,Layer.SERVICES, Subsystem.MAIL_SERVICES);
          exDetails.setProperties(mailProperties);
    
        throw new MailServiceException(ex,exDetails);
    }
}


Intercepció i tractament de les Excepcions

Mitjançant l'ús de la programació orientada a aspectes (AOP) i les classes proporcionades pel servei d'excepcions s'obté una solució integral a la gestió intel.ligent d'excepcions.

La intercepció de les excepcions es realitza mitjançant el mecanisme definit als fitxers de configuració (veure apartat 'Configuració i Instal.lació). Per definir un gestor s'han de seguir els següents pasos:

  1. Extendre de la classe 'ExceptionHandlerAdapter'
  2. Definir el mètode 'public void handleException(Throwable e) throws Throwable'
  3. Dins aquest mètode realitzar la implementació del tractament
  4. Si l'excepció rebuda és de negoci encapsular-la en una excepció de tipus 'WrappedCheckedException'. D'aquesta forma capes superiors no hauran de definir la declaració de l'excepció ni capturar-la.
    public class XXXExceptionHandler extends ExceptionHandlerAdapter {
    ...
        public void handleException(Throwable e) throws Throwable {
             ...
            WrappedCheckedException wExc = new WrappedCheckedException(e,...);
            throws wExc;
           }
        }
    ...
    }


  • Generar mitjançant el Servei de Traces un missatge de tipus informatiu de l'excepció.
  • Generar mitjançant el Servei de Traces un missatge de diferent nivell segons el nivell de l'excepció rebuda (es pot obtenir mitjançant l'atribut 'level' de l'objecte 'ExceptionDetails').
  • Integració amb serveis externs que necessiten del coneixement de determinades excepcions
  • Altres necessitats

Exemple:

package net.gencat.ctti.canigo.services.exceptions.handlers;
...
public class SystemExceptionHandler extends ExceptionHandlerAdapter {
...
    public void handleException(Throwable e) throws Throwable {
  	Log log = this.loggingService.getLog(this.getClass());
  	log.debug("Exception received: SystemExceptionHandler, Exception catched:" + e);
      	throw e;
    }
...
}


Eines de Suport

Generació del bytecode d'intercepció amb AspectWerkz

Per tal de què funcioni en execució el mecanisme d'intercepció cal un procés addicional de compilació a les classes. Aquest procés addicional es pot executar en Maven mitjançant el goal 'aspectwerkz:weave' tal i com es mostra en la figura següent:



Aquest procés fa servir les següents variables de configuració que hauran d'estar en el nostre fitxer 'project.properties':
<!-- mode de màxima informació en el procés de modificació de bytecode  -->
maven.aspectwerkz.verbose=true

<!-- compilació basada en  anotacions  -->
maven.aspectwerkz.mode=attribdef
maven.aspectwerkz.definition.validate=true

<!--  fitxer de propietats del servei (veure Configuració) o de definició del nostre  aspecte  -->
maven.aspectwerkz.definition.file.src=$\{basedir\}/src/main/resources/aop.xml

<!--  directori a on es copien totes les classes que s'han de passar pel nostre procés
addicional de modificació de bytecode  -->
maven.aspectwerkz.build.dest=$\{basedir\}/target/aspectwerkz/classes

<!--  directori a on s'ubicaran les classes modificades. És important que aquest  directori sigui
el directori WEB-INF/classes de l'aplicació desplegada al  servidor  -->
maven.aspectwerkz.weave.build.dir=$\{basedir\}/.deployables/$\{  ctti.war.wtp.module\}/WEB-INF/classes




Per evitar la necessitat manual de realitzar aquest goal, podem incorporar el següent codi 'Maven' en la creació de l'aplicatiu Web o de la llibreria:
<u:available  file="$\{maven.aspectwerkz.weave.build.dir\}/aop.xml">
	<attainGoal  name="aspectwerkz:weave"/>
	<ant:echo>Moving file  $\{maven.aspectwerkz.weave.build.dir\}/aop.xml to
	$\{maven.aspectwerkz.weave.build.dir\}/../aop.xml</ant:echo>
	<ant:move  failonerror="false" file="$\{maven.aspectwerkz.weave.build.dir\}/aop.xml"
	tofile="$\{maven.aspectwerkz.weave.build.dir\}/../aop.xml"/>
</u:available>







Integració amb Altres Serveis

Integració amb el Servei de Presentació (Arquitectura Base)

Aplicar les estratègies definides prèviament implica que l'excepció és llençada a les capes superiors. Aquestes poden decidir si tractar-la o no tractar-la. Si la petició ha estat realitzada per un usuari en mode on-line és convenient que li informem de l'excepció que s'ha produït.

Tota excepció produïda per l'aplicació arriba finalment a la classe principal de l'arquitectura de Canigó 'ExtendedDelegatingTilesRequestProcessor' (veure el document 'Serveis de Presentació' per a més informació).

Aquesta classe captura l'excepció rebuda i realitza els següents pasos:

  1. Extreu la següent informació de detall de l'excepció ('ExceptionDetails'):
  1. Codi d'error
  2. Arguments per construir un missatge a partir del codi d'error
  3. Propietats adicionals de l'excepció.
  4. Nivel de l'error
  5. 'Layer' o capa d'on prové
  6. Subsistema d'on prové
  1. Construeix varis objectes de tipus 'ActionMessage' de Struts a partir d'aquesta informació que afegeix a l'object 'ActionMessages'.
  1. El primer missatge conté el missatge d'error (construit mitjançant el Servei de Multiidioma segons el codi d'error de l'excepció i els arguments).

message = new ActionMessage(exDetails.getErrorCode(),
exDetails.getArguments());

Afegeix aquest missatge a l'objecte 'ActionMessages' amb clau 'org.apache.struts.action.ERROR'.

  1. El segon missatge es construeix amb els detalls de l'excepció. Si l'excepció rebuda és de tipus 'SystemException' la clau del missatge és ' 'net.gencat.ctti.canigo.services.exceptions.Layer.error', mentre que si és de tipus 'WrappedCheckedException' el deixa a ' net.gencat.ctti.canigo.services.exceptions.Layer.warning'.
  1. L'objecte 'ActionMessages' és deixat a l'atribut del request 'Globals.ERROR_KEY' per integració amb Struts.

request.setAttribute(Globals.ERROR_KEY, messages);

Els errors que s'insereixen fan ús del Servei Multidioma. És necessari afegir al fitxer de multiidioma les següents entrades:

net.gencat.ctti.canigo.services.exceptions.WrappedCheckedException=<br>Error  type=\{0\}
<br>Localized message=\{1\}<br>Properties=\{2\}<br>  Layer=\{3\}<br>Subsystem=\{4\}
<br>Error  code=\{5\}<br>Arguments=\{6\}

net.gencat.ctti.canigo.services.exceptions.SystemException=<br>Error  type=\{0\}
<br>Localized message=\{1\}<br>Properties=\{2\}<br>  Layer=\{3\}<br>Subsystem=\{4\}
<br>Error  code=\{5\}<br>Arguments=\{6\}




  1. Finalment, consulta a la configuració de l'acció d'Struts si existeix un 'ActionForward' anomenat 'error'. Si es troba, es redirigeix la petició cap aquest 'forward' (típicament per mostrar la mateixa pantalla d'on venim amb els missatges d'error). Si no es troba, l'excepció puja i es delega la possibilitat del tractament a algun gestor d'excepcions d'Struts (típicament definint dintre del tag <global-exceptions/> l'excepció que volem manegar i el gestor corresponent).

Pàgina per presentar els errors

Podem representar els errors en una pàgina JSP fent ús del tag 'messages' de Struts.

A continuació es mostra un exemple de la seva utilització, on es mostren els missatges en colors diferents segons la severitat de l'error:

<html:messages id="msg"  property="org.apache.struts.action.ERROR">
    <div class="error"  style="margin-right: 10px; margin-bottom: 3px; margin-top: 3px">
        <img  src="images/iconWarning.gif"  class="icon"  alt="Warning" />
        <bean:write name="msg" filter="false"/><a  href="javascript:showEL('errorDetails')">
        <b><bean:message  key="jsp.includes.see_error_details"/></b></a>
        <a  href="javascript:showEL('stackTrace')" >
        <b><bean:message  key="jsp.includes.see_stack_trace"/></b></a>]<br>
        <html:messages  id="msg"  property="net.gencat.ctti.canigo.services.exceptions.Level.WARNING">
            <div  id="errorDetails" style="display: none"><br><font  color="#ff0000">
            <bean:write name="msg"  filter="false"/></font>            </div>
        </html:messages>
        <html:messages  id="msg"  property="net.gencat.ctti.canigo.services.exceptions.Level.ERROR">
            <div  id="errorDetails" style="display: none">
                <font  color="#00cc00"><bean:write name="msg"  filter="false"/></font>
            </div>
        </html:messages>
        <html:messages  id="msg"  property="net.gencat.ctti.canigo.services.exceptions.STACKTRACE">
            <div  id="stackTrace" style="display: none"><br>
                <pre><bean:write  name="msg"  filter="false"/></pre>
            </div>
        </html:messages>
    </div>
</html:messages>